/*
 * Copyright (C) 2010,2011.
 * AHCP Project
 * All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at 
 *
 *     http://www.apache.org/licenses/LICENSE-2.0 
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an "AS IS"
 * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 
 * express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */
package org.jacp.demo.service;

import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Container;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsEnvironment;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Toolkit;
import java.awt.Transparency;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import java.awt.image.VolatileImage;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.WeakHashMap;

import javax.swing.ImageIcon;

import org.im4java.core.ConvertCmd;
import org.im4java.core.IM4JavaException;
import org.im4java.core.IMOperation;
import org.jacp.api.action.IAction;
import org.jacp.api.action.IActionListener;
import org.jacp.demo.globals.GlobalSettings;
import org.jacp.demo.io.ThumbnailExtractor;
import org.jacp.demo.main.ImageDemoLauncher;
import org.jacp.demo.repository.ImageInfoCacheRepository;
import org.jacp.demo.util.DropShadowPanel;
import org.jacp.swing.rcp.component.AStatelessComponent;
import org.jdesktop.swingx.graphics.GraphicsUtilities;

/**
 * Load image thumbnails form cache or create temporary thumbnail and load to
 * panel
 * 
 * @author Andy Moncsek
 * 
 */
public class LoadImageService extends AStatelessComponent {
	private final static int THUMB_WIDTH = GlobalSettings.THUMBNAIL_LONGER_SIDE;
	private final static int THUMB_HEIGHT = GlobalSettings.THUMBNAIL_SHORTER_SIDE;
	private volatile ImageInfoCacheRepository cacheRepository;
	private final static GraphicsConfiguration graphicsConfiguration = GraphicsEnvironment
			.getLocalGraphicsEnvironment().getDefaultScreenDevice()
			.getDefaultConfiguration();
	private ConvertCmd cmd = null;
	private IMOperation op = null;
	private final ThumbnailExtractor extractor = new ThumbnailExtractor();

	@Override
	public Object handleAction(final IAction<ActionEvent, Object> action) {
		final Object message = action.getLastMessage();
		if (message instanceof List<?>) {
			@SuppressWarnings("unchecked")
			final List<DropShadowPanel> panels = (List<DropShadowPanel>) message;
			final WeakHashMap<String, BufferedImage> cacheImageMap = new WeakHashMap<String, BufferedImage>();
			for (int i = 0; i < panels.size(); i++) {
				final DropShadowPanel panelToLoad = panels.get(i);
				final String[] cacheRecord = getCacheRepository()
						.getCacheRecord(panelToLoad.getFilePath());
				if (cacheRecord.length > 1) {
					// found cache entry

					loadCacheEntry(panelToLoad, cacheRecord, cacheImageMap);
				} else {
					// not found
					// use original image
					BufferedImage myPicture = getThubnail(panelToLoad
							.getFilePath());
					if (myPicture != null) {
						myPicture = resize(myPicture, THUMB_WIDTH, THUMB_HEIGHT);
						final BufferedImage shadow = refreshShadow(myPicture);
						panelToLoad
								.setImage(createFinalImage(shadow, myPicture));
						panelToLoad.repaint();
					} else {
						handleError(panelToLoad);
					}
				}

			}
		}
		return null;
	}

	private final VolatileImage createFinalImage(final BufferedImage shadow,
			final BufferedImage original) {
		final VolatileImage finalImage = createVolatileImage(
				original.getWidth() + 15, original.getHeight() + 15,
				Transparency.TRANSLUCENT);
		final Graphics offscreenGraphics = finalImage.getGraphics();

		((Graphics2D) offscreenGraphics).setComposite(AlphaComposite.Src);

		((Graphics2D) offscreenGraphics).setBackground(new Color(0, 0, 0, 0));
		offscreenGraphics.clearRect(0, 0, finalImage.getWidth(),
				finalImage.getHeight());

		if (shadow != null) {
			final int x = (finalImage.getWidth() - shadow.getWidth()) / 2;
			final int y = (finalImage.getHeight() - shadow.getHeight()) / 2;
			offscreenGraphics.drawImage(shadow, x + DropShadowPanel.distance_x,
					y + DropShadowPanel.distance_y, null);
		}

		final int x = (finalImage.getWidth() - original.getWidth()) / 2;
		final int y = (finalImage.getHeight() - original.getHeight()) / 2;
		offscreenGraphics.drawImage(original, x, y, null);

		offscreenGraphics.dispose();

		return finalImage;
	}

	/**
	 * create HW accelerated image
	 * 
	 * @param width
	 * @param height
	 * @param transparency
	 * @return
	 */
	private final VolatileImage createVolatileImage(int width, int height,
			int transparency) {
		GraphicsEnvironment ge = GraphicsEnvironment
				.getLocalGraphicsEnvironment();
		GraphicsConfiguration gc = ge.getDefaultScreenDevice()
				.getDefaultConfiguration();
		VolatileImage image = null;

		image = gc.createCompatibleVolatileImage(width, height, transparency);

		int valid = image.validate(gc);

		if (valid == VolatileImage.IMAGE_INCOMPATIBLE) {
			image = this.createVolatileImage(width, height, transparency);
			return image;
		}

		return image;
	}

	private final BufferedImage refreshShadow(final BufferedImage original) {
		if (original != null) {
			final String key = original.getWidth() + "+" + original.getHeight();
			BufferedImage shadowTemp = DropShadowPanel.shadowCache.get(key);
			if (shadowTemp == null) {
				shadowTemp = createDropShadow(original);
				DropShadowPanel.shadowCache.put(key, shadowTemp);
			}
			return shadowTemp;
		}
		return null;
	}

	/**
	 * create shadow for an image
	 * 
	 * @param image
	 * @return
	 */
	private final BufferedImage createDropShadow(final BufferedImage image) {
		final BufferedImage subject = prepareImage(image);
		applyShadow(subject);
		return subject;
	}

	/**
	 * create transparent image int ARGB format
	 * 
	 * @param image
	 * @return
	 */
	private final BufferedImage prepareImage(final BufferedImage image) {
		final BufferedImage subject = new BufferedImage(image.getWidth()
				+ DropShadowPanel.shadowSize * 2, image.getHeight()
				+ DropShadowPanel.shadowSize * 2, BufferedImage.TYPE_INT_ARGB);

		final Graphics2D g2 = subject.createGraphics();
		g2.drawImage(image, null, DropShadowPanel.shadowSize,
				DropShadowPanel.shadowSize);
		g2.dispose();

		return subject;
	}

	/**
	 * compute shadow
	 * 
	 * @param image
	 */
	private final void applyShadow(final BufferedImage image) {
		final int dstWidth = image.getWidth();
		final int dstHeight = image.getHeight();

		final int left = (DropShadowPanel.shadowSize - 1) >> 1;
		final int right = DropShadowPanel.shadowSize - left;
		final int xStart = left;
		final int xStop = dstWidth - right;
		final int yStart = left;
		final int yStop = dstHeight - right;

		final int shadowRgb = DropShadowPanel.shadowColor.getRGB() & 0x00FFFFFF;

		final int[] aHistory = new int[DropShadowPanel.shadowSize];
		int historyIdx = 0;

		int aSum;

		final int[] dataBuffer = ((DataBufferInt) image.getRaster()
				.getDataBuffer()).getData();
		final int lastPixelOffset = right * dstWidth;
		final float sumDivider = DropShadowPanel.shadowOpacity
				/ DropShadowPanel.shadowSize;

		// horizontal pass

		for (int y = 0, bufferOffset = 0; y < dstHeight; y++, bufferOffset = y
				* dstWidth) {
			aSum = 0;
			historyIdx = 0;
			for (int x = 0; x < DropShadowPanel.shadowSize; x++, bufferOffset++) {
				final int a = dataBuffer[bufferOffset] >>> 24;
				aHistory[x] = a;
				aSum += a;
			}

			bufferOffset -= right;

			for (int x = xStart; x < xStop; x++, bufferOffset++) {
				int a = (int) (aSum * sumDivider);
				dataBuffer[bufferOffset] = a << 24 | shadowRgb;

				// substract the oldest pixel from the sum
				aSum -= aHistory[historyIdx];

				// get the lastest pixel
				a = dataBuffer[bufferOffset + right] >>> 24;
				aHistory[historyIdx] = a;
				aSum += a;

				if (++historyIdx >= DropShadowPanel.shadowSize) {
					historyIdx -= DropShadowPanel.shadowSize;
				}
			}
		}

		// vertical pass
		for (int x = 0, bufferOffset = 0; x < dstWidth; x++, bufferOffset = x) {
			aSum = 0;
			historyIdx = 0;
			for (int y = 0; y < DropShadowPanel.shadowSize; y++, bufferOffset += dstWidth) {
				final int a = dataBuffer[bufferOffset] >>> 24;
				aHistory[y] = a;
				aSum += a;
			}

			bufferOffset -= lastPixelOffset;

			for (int y = yStart; y < yStop; y++, bufferOffset += dstWidth) {
				int a = (int) (aSum * sumDivider);
				dataBuffer[bufferOffset] = a << 24 | shadowRgb;

				// substract the oldest pixel from the sum
				aSum -= aHistory[historyIdx];

				// get the lastest pixel
				a = dataBuffer[bufferOffset + lastPixelOffset] >>> 24;
				aHistory[historyIdx] = a;
				aSum += a;

				if (++historyIdx >= DropShadowPanel.shadowSize) {
					historyIdx -= DropShadowPanel.shadowSize;
				}
			}
		}
	}

	/**
	 * load cached image from FS
	 * 
	 * @param panel
	 * @param stringVektor
	 * @param cacheImageMap
	 */
	private final void loadCacheEntry(final DropShadowPanel panel,
			final String[] stringVektor,
			final WeakHashMap<String, BufferedImage> cacheImageMap) {
		final String smallFile = stringVektor[0];

		BufferedImage myPicture = getScaledBufferedImage(smallFile,
				THUMB_WIDTH, THUMB_HEIGHT);
		if (myPicture == null) {
			// can occur! reload
			final String orig = panel.getFilePath();
			myPicture = getThubnail(orig);
			if (myPicture != null) {
				myPicture = resize(myPicture, THUMB_WIDTH, THUMB_HEIGHT);
			} else {
				handleError(panel);
				return;
			}

		}
		final BufferedImage shadow = refreshShadow(myPicture);
		panel.setImage(createFinalImage(shadow, myPicture));
		panel.repaint();
		final Rectangle bounds = panel.getBounds();
		final Container parent = panel.getParent();
		if (bounds != null && parent != null) {
			parent.repaint(bounds.x - 10, bounds.y, bounds.width + 10,
					bounds.height + 10);
		}

	}

	private final void handleError(final DropShadowPanel panel) {
		System.out.println("ERROR");
		final IActionListener<ActionListener, ActionEvent, Object> listener = getActionListener();
		listener.getAction().addMessage("id01.id10", panel);
		listener.getListener().actionPerformed(
				listener.getAction().getActionEvent());
	}

	/**
	 * get image magic console
	 * 
	 * @return
	 */
	private synchronized final ConvertCmd getCMD() {
		if (cmd == null) {
			cmd = new ConvertCmd();
		}
		return cmd;
	}

	/**
	 * get image magic operation
	 * 
	 * @return
	 */
	private synchronized final IMOperation getImageOperation() {
		if (op == null) {
			op = new IMOperation();
			op.size(160);
			op.addImage();
			op.thumbnail(160);
			op.addImage();
		}
		return op;

	}

	private final BufferedImage getThubnail(final String orig) {
		final BufferedImage tmp = extractor.getThumbnail(new File(orig));
		if (tmp != null) {
			return tmp;
		}
		// create command
		final ConvertCmd cmd = getCMD();

		// create the operation, add images and operators/options
		final IMOperation op = getImageOperation();

		final int lastDot = orig.lastIndexOf('.');

		final String dstImage = orig.substring(0, lastDot - 1) + "_tmp.ccc";

		final File cacheDir = ImageDemoLauncher.getCacheDir();
		// TODO only for unix!!
		final String finalName = dstImage.substring(
				dstImage.lastIndexOf("/") + 1, dstImage.length());
		final String currentCacheFile = cacheDir.getAbsolutePath() + "/"
				+ finalName;
		try {
			cmd.run(op, orig, currentCacheFile);
		} catch (final IOException e) {
			e.printStackTrace();
			return null;
		} catch (final InterruptedException e) {
			e.printStackTrace();
			return null;
		} catch (final IM4JavaException e) {
			e.printStackTrace();
			return null;
		}

		final BufferedImage img = getRawFile(currentCacheFile);
		final File tmpFile = new File(currentCacheFile);
		if (tmpFile.exists()) {
			return tmpFile.delete() == true ? img : null;
		}

		return img;

	}

	private final BufferedImage getScaledBufferedImage(final String imagePath,
			final int width, final int hight) {
		// This code ensures that all the pixels in the image are loaded
		final Image image = new ImageIcon(Toolkit.getDefaultToolkit()
				.createImage(imagePath)).getImage();

		// Create a buffered image with a format that's compatible with the
		// screen
		BufferedImage bimage = null;

		if (image.getWidth(null) > width && image.getHeight(null) > hight) {
			// check landscape or portrait
			final double direction = (double) image.getWidth(null)
					/ (double) image.getHeight(null);
			if (direction > 1) {
				bimage = getConfiguration().createCompatibleImage(width,
						(int) (width / direction));
				// Copy image to buffered image
				final Graphics g = getGraphics(bimage);
				// Paint the image onto the buffered image
				g.drawImage(image, 0, 0, width, (int) (width / direction), null);
				g.dispose();
			} else if (direction < 1) {
				bimage = getConfiguration().createCompatibleImage(
						(int) (width * direction), width);
				// Copy image to buffered image
				final Graphics g = getGraphics(bimage);
				// Paint the image onto the buffered image
				g.drawImage(image, 0, 0, (int) (width * direction), width, null);
				g.dispose();
			} else {

				bimage = getConfiguration().createCompatibleImage(width, width);
				// Copy image to buffered image
				final Graphics g = getGraphics(bimage);
				g.drawImage(image, 0, 0, width, width, null);
				g.dispose();
			}
		}

		return bimage;
	}

	private final Graphics getGraphics(final BufferedImage bufferedImage) {
		final Graphics g = bufferedImage.createGraphics();
		((Graphics2D) g).setRenderingHint(RenderingHints.KEY_INTERPOLATION,
				RenderingHints.VALUE_INTERPOLATION_BILINEAR);

		return g;
	}

	private final GraphicsConfiguration getConfiguration() {
		return graphicsConfiguration;
	}

	/**
	 * returns BufferedImage from jpg
	 * 
	 * @param image
	 * @return
	 */
	private final BufferedImage getRawFile(final String imageName) {
		// This code ensures that all the pixels in the image are loaded
		final Image image = new ImageIcon(Toolkit.getDefaultToolkit()
				.createImage(imageName)).getImage();
		if (image.getWidth(null) < 0) {
			return null;
		}
		// Create a buffered image with a format that's compatible with the
		// screen
		final BufferedImage bimage = new BufferedImage(image.getWidth(null),
				image.getHeight(null), BufferedImage.TYPE_INT_RGB);

		// Copy image to buffered image
		final Graphics g = bimage.createGraphics();
		((Graphics2D) g).setRenderingHint(RenderingHints.KEY_INTERPOLATION,
				RenderingHints.VALUE_INTERPOLATION_BILINEAR);
		// Paint the image onto the buffered image
		g.drawImage(image, 0, 0, null);
		g.dispose();

		return bimage;
	}

	/**
	 * resize buffered image
	 * 
	 * @param myPicture
	 * @param width
	 * @param hight
	 * @return
	 */
	private final BufferedImage resize(BufferedImage myPicture,
			final int width, final int hight) {
		// 88 vs 66 -> 4:3
		if (myPicture.getWidth() > width && myPicture.getHeight() > hight) {
			// check landscape or portrait
			final double direction = (double) myPicture.getWidth()
					/ (double) myPicture.getHeight();
			if (direction > 1) {
				myPicture = GraphicsUtilities.createThumbnail(myPicture, width,
						(int) (width / direction));
			} else if (direction < 1) {
				myPicture = GraphicsUtilities.createThumbnail(myPicture,
						(int) (width * direction), width);
			} else {
				myPicture = GraphicsUtilities.createThumbnail(myPicture, width,
						width);
			}

		}
		return myPicture;
	}

	public ImageInfoCacheRepository getCacheRepository() {
		return cacheRepository;
	}

	public void setCacheRepository(
			final ImageInfoCacheRepository cacheRepository) {
		this.cacheRepository = cacheRepository;
	}

}
